/**
 * Main.cpp
 *
 *
 */

#include <stdio.h>
#include "Estructuras.h"
#include "Utils.h"
#include "Terminador.h"
#include "Controlador.h"
#include "Ascensor.h"
#include "Piso.h"

using namespace std;

void informarParametrosInvalidos(enum ParametrosInvalidos error);
int validarParametros(int argc, char* argv[], Parametros* parametros);

void crearProcesoAscensor( Terminador* terminador, Parametros* parametros, Pipe* pipeLlamadas, PipePersonas pipePersonas );
int crearAscensor( Parametros* parametros, Semaforos semaforos, Pipe* pipeLlamadas, PipePersonas pipePersonas);

void crearProcesoControlador( Terminador* terminador, Parametros* parametros, Semaforos semaforos, Pipe* pipeLlamadas, PipePersonas pipePersonas );
int crearControlador( Semaforos semaforos, Pipe* pipeLlamadas, pid_t pidAscensor );

void crearProcesosPiso( Terminador* terminador, Parametros* parametros, Pipe* pipeLlamadas, PipePersonas pipePersonas );
int crearPiso( Parametros* parametros, Pipe* pipeLlamadas, PipePersonas pipePersonas, int nroPiso );

void ejecutarTerminador( Terminador* terminador, Pipe* pipeLlamadas );

int main(int argc, char *argv[]) {
	int retorno;

	// Se loguea siempre
	Log::getInstance()->setModoDebug( true );

	Parametros parametros;
	if ( ( retorno = validarParametros( argc, argv, &parametros ) ) == 0 ){
		Log::getInstance()->loguear( string ( argv[ 0 ] ) + " " + getStringParametros( &parametros ) );

		// Se crea el objeto para finalizar la ejecucicion de la aplicacion
		Terminador terminador( parametros.tiempo );

		// Se crea el pipe para pasar Llamadas entre los Pisos y el Controlador
		Pipe pipeLlamadas;

		// Se crean los pipes para pasar Personas entre los Pisos y el Ascensor
		PipePersonas pipePersonas;
		Pipe* pipeSuben[ parametros.cantidadPisos +1 ];
		Pipe* pipeBajan[ parametros.cantidadPisos +1 ];
		for ( int i = 0; i <= parametros.cantidadPisos; i++ ){
			pipeSuben[ i ] = new Pipe();
			pipeBajan[ i ] = new Pipe();
		}
		pipePersonas.pipePersonasSuben = &pipeSuben[ 0 ];
		pipePersonas.pipePersonasBajan = &pipeBajan[ 0 ];

		// Se inicia la creacion de procesos
		crearProcesoAscensor( &terminador, &parametros, &pipeLlamadas, pipePersonas );

		// Se eliminan los pipes de personas
		for ( int i = 0; i <= parametros.cantidadPisos; i++ ){
			delete pipeSuben[ i ];
			delete pipeBajan[ i ];
		}
	}

	Log::destruir();
	return retorno;
}

void informarParametrosInvalidos(enum ParametrosInvalidos error) {
	switch (error) {
		case CANTIDAD_INVALIDA: {
			Log::getInstance()->loguear("La cantidad de parametros es invalida.");
			break;
		}
		case CANTIDAD_PISOS_INVALIDA: {
			Log::getInstance()->loguear("La cantidad de pisos ingresados es invalida. Debe ser mayor a 0.");
			break;
		}
		case VELOCIDAD_ASCENSOR_INVALIDA: {
			Log::getInstance()->loguear("La velocidad del ascensor ingresada es invalida. Debe ser mayor a 0 y menor a 3 inclusive.");
			break;
		}
		case CAPACIDAD_ASCENSOR_INVALIDA: {
			Log::getInstance()->loguear("La capacidad máxima del ascensor ingresada es invalida. Debe ser mayor a 0.");
			break;
		}
		case TIEMPO_INVALIDO: {
			Log::getInstance()->loguear("La duracion de la simulación ingresada es invalida. Debe ser mayor a 0.");
			break;
		}
	}
}

int validarParametros(int argc, char* argv[], Parametros* parametros){

	int retorno = 0;

	if (argc != 5) {
		cerr << "Parametros invalidos" << endl;
		cerr << "Uso: " << argv[0] << " [Pisos] [Velocidad] [Capacidad] [Duración]" << endl;
		informarParametrosInvalidos(CANTIDAD_INVALIDA);
		retorno = -1;
	}
	else {
		int i = 1;

		int c = atoi(argv[i++]);
		if (c > 0) parametros->cantidadPisos = c;
		else {
			informarParametrosInvalidos(CANTIDAD_PISOS_INVALIDA);
			retorno = -1;
		}

		c = atoi(argv[i++]);
		if (c > 0 && c <= 3) parametros->velocidadAscensor = c;
		else {
			informarParametrosInvalidos(VELOCIDAD_ASCENSOR_INVALIDA);
			retorno = -1;
		}

		c = atoi(argv[i++]);
		if (c > 0) parametros->capacidadAscensor = c;
		else {
			informarParametrosInvalidos(CAPACIDAD_ASCENSOR_INVALIDA);
			retorno = -1;
		}

		c = atoi(argv[i++]);
		if (c > 0) parametros->tiempo = c;
		else {
			informarParametrosInvalidos(TIEMPO_INVALIDO);
			retorno = -1;
		}
	}

	return retorno;
}

void crearProcesoAscensor( Terminador* terminador, Parametros* parametros, Pipe* pipeLlamadas, PipePersonas pipePersonas ) {
	/* Semaforos utilizados para ls sincronizacion de la memoria
	 * compartida que utilizan tanto el proceso "Ascensor" como el
	 * proceso "Controlador"*/
	Semaforo lectura ( (char*) ARCHIVO_SEMAFORO_LECTURA, 0 );
	Semaforo escritura ( (char*) ARCHIVO_SEMAFORO_ESCRITURA, 1 );
	Semaforos semaforos = { &lectura, &escritura };

	pid_t pid = fork();
	if ( pid == 0 ){
		if ( crearAscensor( parametros, semaforos, pipeLlamadas, pipePersonas ) != 0 ) {
			kill( getppid(), SIGUSR1 );
		}
	} else {
		terminador->setPidAscensor( pid );
		crearProcesoControlador( terminador, parametros, semaforos, pipeLlamadas, pipePersonas );
	}
}

int crearAscensor( Parametros* parametros, Semaforos semaforos, Pipe* pipeLlamadas, PipePersonas pipePersonas){
	// Proceso Ascensor cierra el pipe de Llamadas. Necesario para que el controlador muera solo.
	pipeLlamadas->setearModo(ESCRITURA);
	pipeLlamadas->cerrar();

	Ascensor ascensor( parametros->velocidadAscensor, parametros->capacidadAscensor, parametros->cantidadPisos, semaforos, pipePersonas );
	int retorno = ascensor.inicializar();
	if ( retorno == 0 ) {
		ascensor.funcionar();
	}
	return retorno;
}

void crearProcesoControlador( Terminador* terminador, Parametros* parametros, Semaforos semaforos, Pipe* pipeLlamadas, PipePersonas pipePersonas ){

	pid_t pid = fork();
	if ( pid == 0 ){
		if ( crearControlador( semaforos, pipeLlamadas, terminador->getPidAscensor() ) != 0 ){
			kill( getppid(), SIGUSR1 );
		}
	}
	else {
		terminador->setPidControlador( pid );
		crearProcesosPiso( terminador, parametros, pipeLlamadas, pipePersonas );
	}

}

int crearControlador( Semaforos semaforos, Pipe* pipeLlamadas, pid_t pidAscensor ){
	Controlador controlador( semaforos, pipeLlamadas, pidAscensor );
	int retorno = controlador.inicializar();
	if ( retorno == 0 ) {
		controlador.recibirLlamadas();
	}
	return retorno;
}

void crearProcesosPiso( Terminador* terminador, Parametros* parametros, Pipe* pipeLlamadas, PipePersonas pipePersonas ){
	pid_t pid;

	for ( int nroPiso = 0; nroPiso <= parametros->cantidadPisos; nroPiso++ ){
		pid = fork();
		if ( pid == 0 ){
			if ( crearPiso( parametros, pipeLlamadas, pipePersonas, nroPiso ) != 0 ) {
				kill( getppid(), SIGUSR1 );
			}
			break;
		} else {
			terminador->agregarPidPiso( pid );
			if ( nroPiso == parametros->cantidadPisos ){
				// Se ejecuta el terminador una vez que se terminaron de crear todos los procesos hijos.
				ejecutarTerminador( terminador, pipeLlamadas );
			}
		}
	}
}

int crearPiso( Parametros* parametros, Pipe* pipeLlamadas, PipePersonas pipePersonas, int nroPiso ){
	Piso piso( nroPiso, parametros->cantidadPisos, pipeLlamadas, pipePersonas.pipePersonasSuben[ nroPiso ], pipePersonas.pipePersonasBajan[ nroPiso ] );
	int retorno = piso.inicializar();
	if ( retorno == 0){
		piso.generarLlamadas();
	}
	return retorno;
}

void ejecutarTerminador( Terminador* terminador, Pipe* pipeLlamadas ){
	// Proceso Terminador (padre) cierra el pipe de llamadas. Necesario para que el controlador muera solo.
	pipeLlamadas->setearModo(ESCRITURA);
	pipeLlamadas->cerrar();

	terminador->ejecutar();
}
